// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: GRS.scala 2482 2009-10-07 16:55:33Z nmueller $
package locutor.model.parsing.grgen

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
case class GRSError(message: String) extends Exception(message)

/** This object provides a simple interface to the GRS parser class.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
object GRS extends GRSParser {
  def apply(input: File): Any =
    if(input == null || !(input exists))
      throw new GRSError(format("Physical GRS file may not be null (''{0}'').", input))
    else apply(input.content)
  
  def apply(input: String) =
    if(input == null)
      throw new GRSError("Physical GRS representation may not be null.")
    else parse(input)
  
  def parse(input: String) = phrase(grs)(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new GRSError(format("GRS parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class GRSLexer extends StdLexical with RegexParsers {
  override type Elem = Char
  
//  case class CommandLine(chars: String) extends Token {
//    override def toString = "\""+chars+"\""
//  }
  
  override def token: Parser[Token] = 
    ( string                             ^^ StringLit
    | number                             ^^ NumericLit
    //| "!" ~> rep(chrExcept(EofCh, '\n')) ^^ {case cmd => CommandLine(cmd mkString "")}
    | delim
    | EofCh                             ^^^ EOF
    | id                                 ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}        
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  override def whitespace: Parser[Any] = rep(whitespaceChar | "\\" | '#' ~ rep(chrExcept(EofCh, '\n')))
  
  def string  =      
    ( '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }
    | '\'' ~> rep(charSeq | chrExcept('\'', '\n', EofCh)) <~ '\'' ^^ { _ mkString "" }    
    )
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" |'\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class GRSParser extends XGRSParser {
  /* Fill in abstract defs*/
  type Tokens = GRSLexer
  val lexical: Tokens = new GRSLexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "new", "select", "include", "silence", "delete", "clear", "node", "edge", "type", "is",
    "debug", "xgrs", "validate", "strict", "graph", "on", "off", "dump", "save", "export",
    "import", "echo", "custom", "actions", "redirect", "emit", "sync", "io", "parse", "randomseed",
    "isvisited", "setvisited", "freevisitflag", "resetvisitflag", "map", "set", "help", "quit", "exit", 
    "show", "only", "group", "hidden", "with", "by", "no", "incoming", "outgoing", "any", "shape",
    "color", "textcolor", "bordercolor", "labels", "infotag", "shortinfotag", "nodes", "edges", "num",
    "types", "super", "sub", "attributes", "var", "graphs", "backend", "enable", "disable", "layout",
    "open", "show", "add", "reset", "exclude", "allocvisitflag", "apply", "get",
    "options", "option", "file"
  ) ++ XGRSParser.reserved
  
  lexical.delimiters ++= List("{", "}", "@", "$", ";", ".", "/", "->", "-") ++ XGRSParser.delimiters
  
  /* Define the grammar */
  def grs =
    (( assignment_commands | new_commands | select_commands | include_commands | silence_commands | delete_commands
    |  clear_commands | show_commands | type_commands | debug_commands | xgrs_commands | validate_commands
    |  dump_commands | save_commands | export_commands | import_commands | echo_commands | custom_commands
    |  redirect_commands | sync_commands | parse_commands | random_commands | visit_commands
    |  "help" | "quit" | "exit"
    )*)
  
  def new_commands = (new_graph | new_edge | new_node)
  
  def new_graph = "new" ~ "graph" ~ filename ~ opt(text)
  def new_node  = "new" ~ opt(text) ~ opt(":" ~ ident ~ opt(constructor))
  def new_edge  = "new" ~ graph_elem ~ "-" ~ opt(text) ~ opt(":" ~ ident ~ opt(constructor)) ~ "->" ~ graph_elem
  
  def select_commands = (select_backend | select_graph | select_actions | select_parser)
  
  def select_backend = "select" ~ "backend" ~ filename ~ opt(":" ~ params)
  def select_graph   = "select" ~ "graph" ~ text
  def select_actions = "select" ~ "actions" ~ filename
  def select_parser  = "select" ~ "parser" ~ filename ~ text
  
  def include_commands = "include" ~ filename
  
  def silence_commands = "silence" ~ ("on" | "off")
  
  def delete_commands = (delete_node | delete_edge | delete_graph)
  
  def delete_node  = "delete" ~ "node" ~ graph_elem
  def delete_edge  = "delete" ~ "edge" ~ graph_elem
  def delete_graph = "delete" ~ "graph" ~ opt(text)
  
  def clear_commands = "clear" ~ "graph" ~ opt(text)
  
  def show_commands = (show_elems | show_types | show_attrs | show_var | show_graph | show_list | show_elem)
  
  def show_elems = "show" ~ opt("num") ~ ("nodes" | "edges") ~ opt(opt("only") ~ ident)
  def show_types = "show" ~ ("node" | "edge") ~ opt("super" | "sub") ~ "types" ~ opt(ident)
  def show_attrs = "show" ~ ("node" | "edge") ~ (("attributes" ~ opt(opt("only") ~ ident)) | graph_elem)
  def show_var   = "show" ~ "var" ~ ident
  def show_graph = "show" ~ "graph" ~ text ~ opt(sp_params)
  def show_list  = "show" ~ ("graphs" | "actions" | "backend")
  def show_elem  = graph_elem ~ "." ~ ident
  //def show_attr  = "show" ~ graph_elem ~ "." ~ ident
  
  def type_commands = ("node" | "edge") ~ "type" ~ graph_elem ~ "is" ~ graph_elem
  
  def debug_commands =
    ( debug_apply | debug_xgrs | debug_ed | debug_do_layout
    | debug_get_layout_ops | debug_set_layout_ops | debug_set_layout
    )
  
  def debug_apply          = "debug" ~ "apply" ~ opt("(" ~ ")" ~ "=") ~ rule ~ opt("(" ~ params ~ ")") //XXX
  def debug_xgrs           = "debug" ~ "xgrs" ~ rewrite_seq
  def debug_ed             = "debug" ~ ("enable" | "disable")
  def debug_do_layout      = "debug" ~ "layout"
  def debug_set_layout     = "debug" ~ "set" ~ "layout" ~ opt(text)
  def debug_get_layout_ops = "debug" ~ "get" ~ "layout" ~ "options"
  def debug_set_layout_ops = "debug" ~ "set" ~ "layout" ~ "option" ~ sp_params
  
  def xgrs_commands = "xgrs" ~ rewrite_seq  
  
  def validate_commands = "validate" ~ (opt("strict") | "xgrs" ~ rewrite_seq)
  
  def dump_commands =
    ( dump_graph | dump_set_node | dump_set_edge | dump_set_labels | dump_exclude_elem
    | dump_group_node | dump_tag
    )
  
  def dump_graph        = "dump" ~ "graph" ~ filename
  def dump_set_node     = "dump" ~ "set" ~ "node" ~ opt("only") ~ ident ~ ("color" | "bordercolor" | "shape" | "textcolor") ~ text  
  def dump_set_edge     = "dump" ~ "set" ~ "edge" ~ opt("only") ~ ident ~ ("color" | "textcolor") ~ text
  def dump_set_labels   = "dump" ~ "set" ~ ("node" | "edge") ~ opt("only") ~ ident ~ "labels" ~ ("on" | "off" | text)
  def dump_exclude_elem = "dump" ~ "add" ~ ("node" | "edge") ~ opt("only") ~ ident ~ "exclude"
  def dump_group_node   = "dump" ~ "add" ~ "node" ~ opt("only") ~ ident ~ 
    "group" ~ opt("by" ~ opt("hidden") ~ ("no" | "incoming" | "outgoing" | "any") ~
                    opt(opt("only") ~ ident ~ opt("with" ~ opt("only") ~ ident)))
  def dump_tag          = "dump" ~ "add" ~ ("node" | "edge") ~ opt("only") ~ ident ~
    ("infotag" | "shortinfotag") ~ ident
  
  def save_commands = "save" ~ "graph" ~ filename
  
  def export_commands = "export" ~ filename
  
  def import_commands = "import" ~ (filename)+
    
  def echo_commands = "echo" ~ text
  
  def custom_commands = (custom_graph | custom_actions)
  def custom_graph   = "custom" ~ "graph" ~ opt(sp_params)
  def custom_actions = "custom" ~ "actions" ~ opt(sp_params)
  
  def redirect_commands = "redirect" ~ "emit" ~ filename
  
  def sync_commands = "sync" ~ "io"
  
  def parse_commands = "parse" ~ (("file" ~ filename) | text)
  
  def random_commands = "randomseed" ~ numeric
  
  def visit_commands = ("freevisitflag" | "resetvisitflag") ~ ident
  
  def assignment_commands = opt(graph_elem ~ ".") ~ ident ~ "=" ~ ("allocvisitflag" | text | numeric)

  def sp_params   = rep(text)
  def params      = repsep(text, ",")  
  def graph_elem  = text | "@" ~ "(" ~ text ~ ")"  
  def constructor = "(" ~ opt(("$" ~ "=" ~ text ~ opt("," ~ attributes)) | attributes) ~ ")"
  def filename    = (string | opt("/") ~ ident ~ rep(("." | "/" | "-") ~ ident))  
  def attributes: Parser[Any] = (ident ~ "=" ~ (text | numeric)) ~ (("," ~ attributes)*)
}
